03.Go List And Slice
Table of Contents
Section titled “Table of Contents”- 数组是确定数量元素的集合, 数组元素类型可以不一致
- 数组有容量和长度两个属性
cap() len()
查看数组属性 - 数组的长度和容量始终相等
length == capacity
- 数组元素可以修改, 但是数组长度和容量声明后就不能修改
- 数组中未赋值的元素会使用类型的默认值
strList := [3]string{"hey", "you", "world"} // 定义长度为 3 , 元素类型为字符串的数组 strList := [...]string{"hey", "you", "world"} // 定义元素为字符串的数组, 根据值推断长度和容量 intList := [3]int{1, 2} // [1 2 0] 定义容量长度均为 3, 类型为 int 的数组 intList := [...]int{1, 2} // [1, 2] 容量和长度均为 2 的 int 数组 intArray := [3]int{1, 4} // [1 4 0] 初始化数组, 未定义的值取 0 intArray[1], intArray[2] = 2, 3 // [1 2 3] 修改数组的值 len(strList) // 3 strList 长度为 3 cap(intList) // 3 intList 容量为 3
any 类型数组
Section titled “any 类型数组”any 类型数组的元素可以是任意类型
var anyList [3]any // [<nil> <nil> <nil>] any 类型初始值是 nil anyList[0], anyList[1] = "he", 20000 // [he 2 <nil>] anyList[0] = anyList[0].(string) + " llo" // any 转实际类型操作需要显示声明 anyList[1] = anyList[1].(int) + 1 for index, value := range anyList { fmt.Printf("index: %v, value: %v type: %T\n", index, value, value) } > index: 0, value: hello type: string > index: 1, value: 20000 type: int > index: 2, value: <nil> type: <nil>
数组赋值或作为函数参数时, 传递的都是数组的拷贝
函数若想修改源数组, 传参时需要传入数组地址
func three(list [3]int) { // 参数是数组值传递, 外部数组不变 list[0] = 6 } func third(list *[3]int) { // 参数是数组的指针类型, 修改会同步外部数组 list[0] = 9 } source := [...]int{0,1,2} // 定义 source 数组 copy := source // copy 为 source 的拷贝, 互不影响 third(©) // 传入 copy 地址, 函数内的修改会同步给外部 copy three(source) // 传入 source 的拷贝, 函数内修改不影响外部 source fmt.Printf("source: %v copy: %v\n", source, copy) > source: [0 1 2] copy: [9 1 2]
- 切片是一组数量可变的元素集合
- 切片是引用类型, 切片本身不存储数据, 切片赋值传递的是地址
- 切片会自动扩容以存储所有添加的数据
var
声明切片
make
构造切片
从数组截取切片
type slice struct { // 切片定义 $GOROOT/go/src/runtime/slice.go array unsafe.Pointer // 指向一个数组中某个元素的指针 (每个切片都对应一个数组) len int // 切片长度 cap int // 切片容量 } var strSlice []string // nil 切片声明, 创建一个 nil 切片, 与 nil 相等 intSlice := []int{} // [] 切片声明并初始化, 创建空切片, 与 nil 不相等 boolSlice := make([]bool, 1, 2) // [false] 初始化布尔切片, 长度为 1, 容量为 2 fmt.Println(strSlice == nil) // true nil 切片与 nil 一致 fmt.Println(intSlice == nil) // false 空切片与 nil 不一致
切片也可以从数组中截取一段
切片的长度为截取数据的数量, 切片容量为切片开端到数组结尾数量
list := [...]int{0,1,2,3,4} slice := list[1:3] // [1 2] 截取数组创建切片 Printf("length: %d capacity: %d \n", len(slice), cap(slice)) > length: 2 capacity: 4 // 切片长度为截取元素数量, 容量切片开头元素到数组结尾
声明的切片与从数组截取的切片完全一致, 声明的切片对应一个不可见的数组
切片扩容超过切片容量时, 切片会指向一个更大容量的数组, 并把旧数组的数据复制到新数组
切片是引用类型, 切片不存储数据, 赋值或参数时传递的是地址
切片扩容超过容量后会生成新的切片
切片扩容未容量不会生成新的切片
intSlice := []int{0,0,0,0} // 整数切片初始化, 长度容量均为 4 intArray := intSlice // 切片赋值, 传递的是地址, 两切片指向同一个数组 intArray[0] = 1 // intArray 序号 0 重新赋值, intSlice 跟着变化 intArray = append(intArray, 2) // append 添加元素, 超出容量, 扩容返回新的切片, intArray 变更指向的数组 intArray[1] = 1 // intArray 序号 1 重新赋值 fmt.Printf("intArray: %v intSlice: %v\n", intArray, intSlice) > intArray: [1 1 0 0 2] intSlice: [1 0 0 0] // 切片扩容后, 两切片指定数组不同,值互不影响 slice := make([]int, 3, 5) // 构造长度为 3 容量为 5 的切片 seq := slice // seq slice 指向同一组数据 seq = append(seq, 1) // [0 0 0 1] seq 添加数据, 未超出容量, seq 和 slice 仍指向同一组数据 slice = append(slice, 2) // [0 0 0 2] slice 添加数据, 未超出容量, seq 和 slice 仍指向同一组数据 Printf("seq: %v slice: %v\n", seq, slice) // seq 和 slice 一直指向同一组数据 > seq: [0 0 0 2] slice: [0 0 0 2] // slice 变更覆盖 seq 变更
Golang 1.20 切片的扩容策略 $GOROOT/go/src/runtime/slice.go
func growslice
newcap := old.cap doublecap := newcap + newcap if cap > doublecap { // 所需容量大于两倍的原有容量 newcap = cap // 扩容后容量等于所需容量 } else { // 所需容量小于两倍原有容量情况下 const threshold = 256 // 设置变更策略的容量阈值 if old.cap < threshold { newcap = doublecap // 原有容量小于 256, 新数组容量为原有两倍 } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { // 原有容量大于 256, 旧容量自增 192 加 旧容量的四分之一 // Transition from growing 2x for small slices // to growing 1.25x for large slices. This formula // gives a smooth-ish transition between the two. newcap += (newcap + 3*threshold) / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } }
Golang 扩容增速从 2 倍逐渐减少至 1.25 倍
不同类型切片在扩容时候还有特殊的偏移增长机制
slice := make([]int, 80, 80) for i:=0; i < 2049; i ++ { oldCap := cap(slice) slice = append(slice, 1) newcap := cap(slice) if oldCap != newcap { Printf("int old: %d new %d \n", oldCap, newcap) } } > int old: 80 new 160 > int old: 160 new 336 > int old: 336 new 672 > int old: 672 new 1184 > int old: 1184 new 1696 > int old: 1696 new 2384 > int8 old: 80 new 160 > int8 old: 160 new 320 > int8 old: 320 new 640 > int8 old: 640 new 1024 > int8 old: 1024 new 1536 > int8 old: 1536 new 2304
切片使用 append
添加 删除 插入元素
slice := []int{0,0,0,0} slice = append(slice, []int{1, 1}...) // append 添加数据 [0 0 0 0 1 1] slice = append(slice[:1], slice[3:]...) // append 删除数据 [0 0 1 1] slice = append(slice[:1], append([]int{2}, slice[1:]...)...) fmt.Printf("slice: %v\n", slice) > slice: slice: [0 2 0 1 1] // append 插入数据
切片索引 0 <= index < length <= capacity
截取切片片段 list[low:high]
切片索引不能为负数
截取切片时以 list[low:high:max] 设定长度和容量
list := [...]int{0,1,2,3} list[3] // 3 切片索引 从左往右 0 开始第 3 个数 list[len(list)-1] // 3 切片索引 最后一个数据 list[:2] // [0 1] 从切片开端到索引 2 截取片段 list[1:] // [1 2 3] 从索引 1 到切片结尾 list[1:3] // [1 2] 切片索引扁片(左闭右开) list[:] // [0 1 2 3] 切片全部数据 list[1:2] // 忽略 high [low:] 容量 3 (length - low) list[1:2:3] // 忽略 high [low:max] 容量 2 (max - low)
slice := []int{4,1,6,2} fmt.Println(sort.Ints(slice)) // 切片排序 > [1 2 4 6] copy(destSlice, sourceSlice) // 切片复制, dest 容量会影响复制结果